/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package MessageHandler;

import configuration.config_manager;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import postmaster.peer;
import postmaster.postmaster;

/**
 *
 * @author maniksin
 */
public class messagehandler {
    
    public messages msg_id;      // 4 byte
    public String source;
    public String dest;  
    public message_type type;  // 4 byte
    public submessage sub_msg_id;  // 4 byte
    public String user_ip;      // IP of the application user. Version number for hello packet
    public int user_port;
    public String cmd_string[];
    
    private final static int MAX_PEER_NAME_LEN = 50;
    public final static int FIXED_PAYLOAD_LEN = 32;
    private final static int MAX_CMD_STRING_LEN = 500;
    public final static int MAX_CMD_STRINGS = 20;
    public final static int MAX_MSG_PAYLOAD_LEN = 1000;
    
    /*
     * Message format:
     */
    public static peer find_sender(DatagramPacket dp, Set peers) {
        peer next_peer;
        
        InetAddress ip;
        int port;
        
        ip = dp.getAddress();
        port = dp.getPort();
        
        Iterator i = peers.iterator();
        while (i.hasNext()) {
            next_peer = (peer) i.next();
            if (next_peer == null) {
                System.err.println("NEXT IS NULL");
            }
            if (next_peer.matches(ip, port)) {
                return next_peer;                
            }
        }
        
        return null;
        
    }
    public static messagehandler decode_message(DatagramPacket dp) {
        ByteBuffer bb;
        messagehandler msg = new messagehandler();
        int length;
        byte[] ba = dp.getData();
        byte[] peer_name;
        byte[] cmd_string;
        int total_length;
        
        /*
         * int: msg_id
         * int: my_name_len
         * string: source
         * int: peer_name_len
         * string: dest
         * int: user_ip_address_length;
         * string: user_ip_address
         * int: user_port
         * int: msg_type
         * int: sub_msg_id;
         * int: num_strings
         * <REPEAT> =>
         * int: cmd_string_len
         * string: cmd_string
         * <END REPEAT>
         */
        
        total_length = FIXED_PAYLOAD_LEN;
        
        bb = ByteBuffer.wrap(ba);
        msg.msg_id = messages.find_enum(bb.getInt());
        
        length = bb.getInt();
        total_length += length;
        if (length > MAX_PEER_NAME_LEN) {
            Logger.global.log(Level.WARNING, "Too large peer name in packet ({0}) bytes. Decoding aborted!", length);
            return null;
        } else {
            peer_name = new byte[length];
            bb.get(peer_name, 0, length);
            msg.source = new String(peer_name);
        }
        
        //msg.peer_name = new byte[PEER_NAME_LEN];
        length = bb.getInt();   // peer name length
        total_length += length;
        if (length > MAX_PEER_NAME_LEN) {
            Logger.global.log(Level.WARNING, "Too large peer name in packet ({0}) bytes. Decoding aborted!", length);
            return null;
        } else {
            peer_name = new byte[length];
            bb.get(peer_name, 0, length);
            msg.dest = new String(peer_name);
        }
        
        // Get user_ip
        length = bb.getInt(); // user ip string
        total_length += length;
        peer_name = new byte[length];
        bb.get(peer_name, 0, length);
        msg.user_ip = new String(peer_name);
        
        msg.user_port = bb.getInt();
                
        msg.type = message_type.find_enum(bb.getInt());
        msg.sub_msg_id = submessage.get_submessage(bb.getInt());
        
        length = bb.getInt();       // num strings
        total_length += length;
        if (length > MAX_CMD_STRINGS) {
            Logger.global.log(Level.WARNING, "Too many arguments in message ({0}). Message not handled.", length);
            return null;
        }
        msg.cmd_string = new String[length];
        
        int i;
        for (i = 0; i < msg.cmd_string.length; i++) {
        
            length = bb.getInt();
            total_length += (length + 4);   // 4 bytes of length itself
            if (length > MAX_CMD_STRING_LEN) {
                Logger.global.log(Level.WARNING, "Too large argument text in packet ({0}) bytes. Decoding aborted!", length);
                return null;
            } else if (total_length > MAX_MSG_PAYLOAD_LEN) {
                Logger.global.log(Level.WARNING, "Received packet too large (> {0}) bytes. Decoding aborted!", total_length);
                return null;
            } else {
                if (length > 0) {
                    cmd_string = new byte[length];
                    bb.get(cmd_string, 0, length);
                    msg.cmd_string[i] = new String(cmd_string);    
                } else {
                    msg.cmd_string[i] = null;
                }
                
            }
        }
        
        return msg;
    }
    
    public static DatagramPacket create_reply(messages msg_id, DatagramPacket user_dp, String my_name, String reply_string) {
        messagehandler msg = new messagehandler();
        messagehandler user_msg = decode_message(user_dp);
        DatagramPacket dp;
        
        msg.msg_id = msg_id;
        msg.source = my_name;
        msg.dest = user_msg.source;
        msg.type = message_type.ACKNOWLEDGE_NONE;
        msg.sub_msg_id = user_msg.sub_msg_id;
        msg.user_ip = user_msg.user_ip;
        msg.user_port = user_msg.user_port;
        msg.cmd_string = new String[1];
        msg.cmd_string[0] = reply_string;
        
        dp = create_message(msg);
        
        return dp;
        
    }
    
    public static DatagramPacket forward_reply(DatagramPacket reply_dp) {
        DatagramPacket user_dp;
        String fwd_ip;
        int fwd_port;
        
        
        //messagehandler msg = new messagehandler();
        messagehandler user_msg = decode_message(reply_dp);
        
        user_msg.msg_id = messages.USER_REPLY;
        /*
        user_msg.peer_name = msg.peer_name;
        user_msg.type = msg.type;
        user_msg.sub_msg_id = msg.sub_msg_id;
        */
        fwd_ip = user_msg.user_ip;
        fwd_port = user_msg.user_port;
        Logger.global.log(Level.INFO, "Forwarding reply to the client: {0}/{1}.", new Object[]{fwd_ip, fwd_port});
        
        user_msg.user_ip = "0.0.0.0";
        user_msg.user_port = 0;
        //user_msg.cmd_string = msg.cmd_string;
        
        user_dp = create_message(user_msg);
        try {
            user_dp.setAddress(InetAddress.getByName(fwd_ip));
        } catch (UnknownHostException ex) {
            Logger.global.log(Level.WARNING, "Invalid user IP embedded in received reply ({0}). Reply not forwarded", fwd_ip);
            return null;
        }
        user_dp.setPort(fwd_port);
        
        return user_dp;
    }
    public static DatagramPacket create_command(DatagramPacket user_dp, config_manager conf) {
        DatagramPacket cmd_dp;
        messagehandler msg = new messagehandler();
        messagehandler user_msg = decode_message(user_dp);
        
        Logger.global.log(Level.INFO, "Sending command: <{0}> to {1}.", new Object[]{user_msg.sub_msg_id.toString(), user_msg.dest});
        
        msg.msg_id = messages.PEER_COMMAND;
        msg.source = conf.my_name;
        msg.dest = user_msg.dest;
        msg.type = user_msg.type;
        msg.sub_msg_id = user_msg.sub_msg_id;
        msg.user_ip = user_dp.getAddress().getHostAddress();
        msg.user_port = user_dp.getPort();
        msg.cmd_string = user_msg.cmd_string;
        
        cmd_dp = create_message(msg);
        
        return cmd_dp;
    }
    public static DatagramPacket create_message(messagehandler msg) {
        byte[] ba;
        int total_length;
        int remaining_bytes = 0;
        
        /*
         * int: msg_id
         * int: source length
         * string: source
         * int: dest length
         * string: dest
         * int: user_ip_address_length;
         * string: user_ip_address
         * int: user_port
         * int: msg_type
         * int: sub_msg_id;
         * int: num_strings
         * <REPEAT> =>
         * int: cmd_string_len
         * string: cmd_string
         * <END REPEAT>
         */
        
        byte[] peer_name;
        byte[] cmd_string;
        byte[] user_ip_str;
        
        if (msg.source.length() > MAX_PEER_NAME_LEN) {
            Logger.global.log(Level.WARNING, "Too large peer name {(0)}. Not sent to peer", msg.source);
            return null;
        }
        if (msg.dest.length() > MAX_PEER_NAME_LEN) {            
            Logger.global.log(Level.WARNING, "Too large peer name ({0}. Not sent to the peer", msg.dest);
            return null;
        }
        
        if (msg.cmd_string.length > MAX_CMD_STRINGS) {
            Logger.global.log(Level.WARNING, "Too many arguments in message ({0}). Message not created.", msg.cmd_string.length);
            return null;
        }
        
        int i;
        total_length = 0;
        for (i = 0; i < msg.cmd_string.length; i++) {
            total_length += 4;  // string length
            
            if (msg.cmd_string[i] != null) {
                total_length += msg.cmd_string[i].length();
                
                if (msg.cmd_string[i].length() > MAX_CMD_STRING_LEN) {
                    Logger.global.log(Level.WARNING, "Too large argument length ({0}). Message not created.", msg.cmd_string[i]);
                    return null;
                }
            }
        }
        
        total_length += FIXED_PAYLOAD_LEN + msg.source.length() + 
                msg.user_ip.length() + msg.dest.length();
        
        if (total_length > MAX_MSG_PAYLOAD_LEN) {
            Logger.global.log(Level.WARNING, "Too large message ({0} bytes). Message not created.", total_length);
            return null;
        }
        
        ba = new byte[total_length];
        DatagramPacket dp = new DatagramPacket(ba, ba.length);
        
        ByteBuffer bb = ByteBuffer.wrap(ba);
        
        bb.putInt(msg.msg_id.msg_id);
        
        try {
            peer_name = msg.source.getBytes("UTF8");
        } catch (UnsupportedEncodingException ex) {
            JOptionPane.showMessageDialog(null, "Error: UTF8 not supported", "Error1", 
                    JOptionPane.ERROR_MESSAGE);
            return null;
        }
        
        bb.putInt(peer_name.length);
        bb.put(peer_name);
        
        try {
            peer_name = msg.dest.getBytes("UTF8");
        } catch (UnsupportedEncodingException ex) {
            JOptionPane.showMessageDialog(null, "Error: UTF8 not supported", "Error1", 
                    JOptionPane.ERROR_MESSAGE);
            return null;
        }
        
        bb.putInt(peer_name.length);
        bb.put(peer_name);
        
        bb.putInt(msg.user_ip.length());
        try {
            user_ip_str = msg.user_ip.getBytes("UTF8");
        } catch (UnsupportedEncodingException ex) {
            JOptionPane.showMessageDialog(null, "Error: UTF8 not supported", "Error0", 
                    JOptionPane.ERROR_MESSAGE);
            return null;
        }
        bb.put(user_ip_str);
        bb.putInt(msg.user_port);
                
        bb.putInt(msg.type.ack_type);
        
        bb.putInt(msg.sub_msg_id.sub_message);
        bb.putInt(msg.cmd_string.length);
        
        for (i = 0; i < msg.cmd_string.length; i++) {
            
            // We can have null strings in the message
            if (msg.cmd_string[i] != null) {
                
                try {
                    cmd_string = msg.cmd_string[i].getBytes("UTF8");
                } catch (UnsupportedEncodingException ex) {
                    JOptionPane.showMessageDialog(null, "Error: UTF8 not supported", "Error1", 
                            JOptionPane.ERROR_MESSAGE);
                    return null;
                }
        
                bb.putInt(cmd_string.length);
                bb.put(cmd_string);                
            } else {
                bb.putInt(0);
            }
            
        }
        
        return dp;
    }
    
    public static void handle_peer_command(DatagramPacket dp, peer p, postmaster pm) {
        messagehandler msg = decode_message(dp);
        
        handler_thread th = new handler_thread(dp, p, pm);
        th.start();

    }
    public static void handle_message(DatagramPacket dp, postmaster pm) {
        
        messagehandler msg = decode_message(dp);
        peer p = peer.get_peer_by_name(msg.source, pm.peers);
        
        switch(msg.msg_id) {
            case PEER_COMMAND:
            case USER_COMMAND:
                //String reply = handle_command(msg);
                handle_peer_command(dp, p, pm);                
                break;
                
            case PEER_REPLY:
                DatagramPacket reply_dp = forward_reply(dp);
                pm.send_message(reply_dp);
                break;
                                
            case USER_REPLY:
                Logger.global.warning("USER_REPLY received by postmaster!! Not handled.");
                break;
                
            default:
                break;
        }
    }
}
